package testresultimpl

import (
	"bytes"
	"encoding/csv"
	"fmt"
	"io"
	"net/http"
	"strings"

	"github.com/astaxie/beego"
	"github.com/opensourceways/server-common-lib/utils"
	"github.com/sirupsen/logrus"

	"cvevulner/cve-ddd/domain"
)

func NewTestResultImpl(log *logrus.Entry) *testResultImpl {
	return &testResultImpl{
		log:    log,
		client: utils.NewHttpClient(3),
	}
}

type testResultImpl struct {
	log    *logrus.Entry
	client utils.HttpClient

	// map[branch]map[component][]rpm
	resultCache map[string]map[string][]rpm
}

type rpm struct {
	Name   string
	IsEpol bool
}

func (impl *testResultImpl) getCsvOfRpmByBranch(branch, date string) []byte {
	url := fmt.Sprintf("%s/repo.openeuler.org/%s/%s/%s.csv",
		beego.AppConfig.String("testResult::host"), branch, date, branch,
	)

	req, _ := http.NewRequest(http.MethodGet, url, nil)

	content, _, err := impl.client.Download(req)
	if err != nil {
		// 可能有些分支没有转测，所以出错后记录日志即可
		impl.log.Errorf("get csv rpm of [%s %s] failed: %s", branch, date, err.Error())
	}

	return content
}

func (impl *testResultImpl) getCsvOfRpmInEpolByBranch(branch, date string) []byte {
	epolUrl := fmt.Sprintf("%s/repo.openeuler.org/%s/EPOL/%s/main/%s.csv",
		beego.AppConfig.String("testResult::host"), branch, date, branch,
	)

	req, _ := http.NewRequest(http.MethodGet, epolUrl, nil)

	content, _, err := impl.client.Download(req)
	if err != nil {
		impl.log.Errorf("get epol csv rpm of [%s %s] failed: %s", branch, date, err.Error())
	}

	return content
}

func (impl *testResultImpl) Init(handleBranch []string, date string) {
	impl.resultCache = make(map[string]map[string][]rpm)

	for _, b := range handleBranch {
		var cacheNormal, cacheEpol map[string][]rpm

		content := impl.getCsvOfRpmByBranch(b, date)
		cacheNormal = impl.parseContent(content, false)

		epolContent := impl.getCsvOfRpmInEpolByBranch(b, date)
		cacheEpol = impl.parseContent(epolContent, true)

		cacheMerged := impl.mergeCache(cacheNormal, cacheEpol)
		if len(cacheMerged) == 0 {
			continue
		}

		impl.resultCache[b] = cacheMerged
	}
}

func (impl *testResultImpl) mergeCache(source ...map[string][]rpm) map[string][]rpm {
	target := make(map[string][]rpm)
	for _, m := range source {
		for k, v := range m {
			target[k] = append(target[k], v...)
		}
	}

	return target

}

func (impl *testResultImpl) Filter(cves domain.Cves) domain.Cves {
	var filtered domain.Cves

	for _, cve := range cves {
		var filteredVersion []string
		for _, av := range cve.AffectedVersion {
			if _, ok := impl.resultCache[av][cve.Component]; ok {
				filteredVersion = append(filteredVersion, av) // 在转测数据对应的分支能找到对应的包，该cve的该分支才能发布公告
			}
		}

		if len(filteredVersion) == 0 {
			impl.log.Errorf("cant find any test repo data of %s", cve.CveNum)

			continue // 过滤后的受影响分支为空，整个cve都不用发布公告了
		}

		cve.AffectedVersion = filteredVersion
		filtered = append(filtered, cve)

		impl.log.Infof("the affected version of %s filtered by test repo are: %v", cve.CveNum, cve.AffectedVersion)
	}

	return filtered
}

func (impl *testResultImpl) IsHotPatchExist(patch *domain.HotPatchIssue, hotDate string) bool {
	patchName := patch.SourcePatchName()
	if patchName == "" {
		return false
	}

	url := fmt.Sprintf("%s/repo.openeuler.org/%s/%s/source/Packages/%s",
		beego.AppConfig.String("testResult::host"), patch.Branch, hotDate, patchName,
	)

	resp, err := http.Head(url)
	if err != nil {
		impl.log.Errorf("check hotPatch rpm %s failed: %s", url, err.Error())
		return false
	}

	return resp.StatusCode == http.StatusOK
}

func (impl *testResultImpl) GenerateProductTree(component string, affectedVersion []string) domain.ProductTree {
	tree := make(domain.ProductTree)

	for _, version := range affectedVersion {
		rpms, ok := impl.resultCache[version][component] // 生成公告前已经过滤一次了，所以组件和影响分支必定存在
		if !ok {
			impl.log.Errorf("cant find product tree of [%s %s]", version, component)
			continue
		}

		for _, v := range rpms {
			// example of rpm: zbar-0.22-4.oe2203.src.rpm
			t := strings.Split(v.Name, ".")
			arch := t[len(t)-2]
			productId := strings.Join(t[:len(t)-3], ".")

			product := domain.Product{
				ID:       productId,
				CPE:      version,
				FullName: v.Name,
				IsEpol:   v.IsEpol,
			}

			tree[arch] = append(tree[arch], product)
		}
	}

	return tree
}

func (impl *testResultImpl) parseContent(content []byte, fromEpol bool) map[string][]rpm {
	buff := bytes.NewBuffer(content)
	r := csv.NewReader(buff)

	componentAndRpm := make(map[string][]rpm)
	for {
		line, err := r.Read()
		if err == io.EOF {
			break
		}

		if err != nil {
			continue
		}

		splitRpm := strings.Fields(line[1])
		if len(splitRpm) == 0 {
			continue
		}

		var t []rpm
		for _, v := range splitRpm {
			t = append(t, rpm{Name: v, IsEpol: fromEpol})
		}

		splitComponent := strings.Split(line[0], ":")
		if len(splitComponent) == 0 {
			continue
		}

		componentAndRpm[splitComponent[0]] = append(componentAndRpm[splitComponent[0]], t...)
	}

	return componentAndRpm
}
